<?php

/*
 * 上传类，支持多文件上传
 */

class Upload extends Base {
	
	//上传路径
	protected $upload_path = './uploads/';
	
	//上传文件大小最大限制，单位KB，0不限制
	protected $max_size = 1024;
	
	//上传图片最大宽度，单位像素，0不限制
	protected $max_width = 0;
	
	//上传图片最大高度，单位像素，0不限制
	protected $max_height = 0;
	
	//允许上传的文件类型(后缀名)，如array('jpg','gif')或者jpg|gif或jpg,gif
	protected $allowed_types = array();
	
	//文件名称前缀
	protected $prefix = '';
	
	//上传文件名称，设置的时候无需后缀名，返回时的结果是带后缀名的
	protected $file_name = '';
	
	//当文件名存在的时候，是否覆盖。如不覆盖会以_1的后缀形式重命名
	protected $override = TRUE;
	
	//============================================================================================//
	
	//上传文件的临时文件地址（绝对地址），路径+名称
	protected $file_temp = '';
	
	//上传文件名称（原始名称，覆盖重命名之前的名称），仅名称而已
	protected $original_name = '';
	
	//上传文件的客服端名称，仅名称而已
	protected $client_name = '';
	
	//上传文件大小
	protected $file_size = 0.00;
	
	//上传文件类型（MIME类型，如image/jpeg）
	protected $file_type = '';
	
	//上传文件后缀名，如jpg
	protected $file_extension = '';
	
	//图片文件宽度，单位px
	protected $image_width = 0;
	
	//图片文件高度，单位px
	protected $image_height = 0;
	
	//上传文件是否图片
	protected $is_image = FALSE;
	
	//待返回的结果集
	protected $datas = array();
	
	protected $_valid = array(
		'upload_path',
		'allowed_types',
		'max_size',
		'max_width',
		'max_height',
		'prefix',
		'file_name',
		'override'
	);

	public function __construct($config = array())
	{
		if ($config)
		{
			$this->config($config);
		}
	}
	
	/*
	 * 初始化
	 * @access	public
	 * @return	boolean
	 */
	public function init()
	{
		if ($this->validate_upload_path() == FALSE)
		{
			return FALSE;
		}
		
		return TRUE;
	}
	
	/*
	 * 上传文件，支持多文件上传
	 * @access	public
	 * @param	$field string 上传文件表单字段名称
	 * @return	boolean
	 */
	public function do_upload($field = 'file')
	{
		$files = $this->get_files($field);
		
		if ( ! $files)
		{
			$this->set_error('上传文件找不到！');
			return FALSE;
		}
		
		//循环的将上传文件保存起来
		foreach ($files as $file)
		{
			if ( ! $this->upload_file($file))
			{
				return FALSE;
			}
		}
		
		return TRUE;
	}
	
	/*
	 * 单文件上传
	 * @access	public
	 * @param	$file array 文件，来自$_FILES
	 * @param	$move boolean 是否立刻移动到上传目录
	 * @return	boolean
	 */
	public function upload_file($file)
	{	
		if ( ! is_array($file) || ! isset($file['tmp_name']))
		{
			$this->set_error('上传文件找不到！');
			return FALSE;
		}
		
		//判断上传文件错误
		if ( ! is_uploaded_file($file['tmp_name']))
		{
			//详细见PHP手册特点=》文件上传处理=》错误信息说明
			//http://cn2.php.net/manual/en/features.file-upload.errors.php
			switch ($file['error'])
			{
				case 1: //UPLOAD_ERR_INI_SIZE
					$this->set_error('上传的文件超过了系统限制的值！');
					break;
				case 2: //UPLOAD_ERR_FORM_SIZE
					$this->set_error('上传文件的大小超过了表单中选项指定的值！');
					break;
				case 3: //UPLOAD_ERR_PARTIAL
					$this->set_error('文件只有部分被上传！');
					break;
				case 4: //UPLOAD_ERR_NO_FILE
					$this->set_error('没有文件被上传！');
					break;
				case 6: //UPLOAD_ERR_NO_TMP_DIR
					$this->set_error('找不到临时文件夹！');
					break;
				case 7: //UPLOAD_ERR_CANT_WRITE
					$this->set_error('文件写入失败！');
				case 8: //UPLOAD_ERR_EXTENSION
					$this->set_error('文件扩展名被禁止！');
					break;
				default :
					$this->set_error('未知错误！');
					break;
			}

			return FALSE;
		}
		
		//对文件的一些属性赋值
		$this->file_temp = str_replace('\\', '/', $file['tmp_name']);
		$this->file_type = $file['type'];
		$this->file_size = round($file['size']/1024, 1);
		$this->file_extension = $this->get_extension($file['name']);
		$this->client_name = $file['name'];
		
		//判断文件大小
		if ( ! $this->is_allowed_filesize())
		{
			$this->set_error('文件大小超过限制！');
			return FALSE;
		}
		
		//判断文件类型
		if ( ! $this->is_allowed_filetype())
		{
			$this->set_error('不允许上传的文件类型！');
			return FALSE;
		}
		
		//上传文件是否图片
		$this->validate_is_image();
		
		if ($this->is_image && ! $this->is_allowed_dimensions())
		{
			$this->set_error('图片尺寸超过限制！');
			return FALSE;
		}
		
		//设定上传文件名称
		if ( ! $this->file_name)
		{
			//没有提供名称，系统生成
			$this->file_name = substr(md5($this->client_name), 0, 20);
		}
		else
		{
			//把后缀名去掉
			$this->file_name = preg_replace('/'.$this->file_extension.'$/i', '', $this->file_name);
		}
		
		//把名称与后缀名组成成完整的文件名
		$this->file_name = $this->prefix.$this->file_name.'.'.$this->file_extension;
		
		$this->original_name = $this->file_name;
		
		//上传文件名称重命名，如果设定了重命名
		$this->file_name = $this->override_file_name($this->file_name);
		
		if ( ! $this->move_file())
		{
			//上传错误 将之前上传的文件删除
			$this->unlink_file();
			$this->set_error('移动文件错误！');
			return FALSE;
		}
		
		//保存数据
		$this->save_data();
		
		return TRUE;
	}
	
	/*
	 * 返回一条上传文件数据，可根据索引返回，从1开始
	 * @access	public
	 * @return	array
	 */
	public function get_data($index = 1)
	{
		return isset($this->datas[$index-1]) ? $this->datas[$index-1] : array();
	}
	
	/*
	 * 返回所有上传文件数据
	 * @access	public
	 * @return	array
	 */
	public function get_datas()
	{
		return $this->datas;
	}
	
	/*
	 * 验证上传目录
	 * @access	protected
	 * @return	boolean
	 */
	protected function validate_upload_path()
	{
		if ( ! $this->upload_path)
		{
			$this->set_error('上传目录未设置！');
			return FALSE;
		}

		$this->upload_path = str_replace('\\', '/', realpath($this->upload_path));
		
		if ( ! is_dir($this->upload_path))
		{
			$this->set_error('上传目录不存在！');
			return FALSE;
		}
		
		if ( ! is_writable($this->upload_path))
		{
			$this->set_error('上传目录无写入权限！');
			return FALSE;
		}
		
		$this->upload_path = rtrim($this->upload_path, '/').'/';
		
		return TRUE;
	}
	
	/*
	 * 检查是否图片，一般情况下的图片为jpg,gif,png
	 * @access	protected
	 * @return	void
	 */
	protected function validate_is_image()
	{
		$png_mimes  = array('image/x-png');
		$jpeg_mimes = array('image/jpg', 'image/jpe', 'image/jpeg', 'image/pjpeg');
		
		if (in_array($this->file_type, $png_mimes))
		{
			$this->file_type = 'image/png';
		}
		
		if (in_array($this->file_type, $jpeg_mimes))
		{
			$this->file_type = 'image/jpeg';
		}
		
		$img_mimes = array('image/gif', 'image/jpeg', 'image/png');
		
		if (in_array($this->file_type, $img_mimes))
		{
			if (function_exists('getimagesize'))
			{
				$d = getimagesize($this->file_temp);
				if ($d)
				{
					$this->image_width = $d[0];
					$this->image_height = $d[1];
					$this->is_image = TRUE;
				}
			}
		}
	}
	
	/*
	 * 从表单字段中获取文件
	 * @access	public
	 * @param	$field string 上传文件表单字段名称
	 * @return	array
	 */
	public function get_files($field = 'file')
	{
		if ( ! isset($_FILES[$field]) || ! $_FILES[$field])
		{
			return array();
		}
		
		$files = array();
		
		if (is_array($_FILES[$field]['name']))
		{
			for ($i=0; $i<count($_FILES[$field]['name']); $i++)
			{
				if ($_FILES[$field]['name'][$i])
				{
					$file = array();
					foreach (array_keys($_FILES[$field]) as $key)
					{
						$file[$key] = $_FILES[$field][$key][$i];
					}
					$files[] = $file;
				}
			}
		}
		else
		{
			if ( ! $_FILES[$field]['name'])
			{
				return array();
			}
			$files[] = $_FILES[$field];
		}
		
		return $files;
	}
	
	/*
	 * 覆盖重命名
	 * @access	protected
	 * @param	$i integer 递归时用到的名称索引
	 * @param	$file_name string 名称
	 * @return	void
	 */
	protected function override_file_name($file_name, $i = 1)
	{
		if ( FALSE == $this->override && file_exists($this->upload_path.$file_name))
		{
			$file_name = preg_replace('/^(.*)\.'.$this->file_extension.'$/', '\\1_'.$i.'.'.$this->file_extension, $this->file_name);
			$i++;
			return $this->override_file_name($file_name, $i);
		}
		else
		{
			return $file_name;
		}
	}
	
	/*
	 * 清除名称中的非法字符（用在自定义名称时）
	 * @access	public
	 * @return	void
	 */
	public function clean_file_name($file_name)
	{
		$bad = array(
			"<!--",
			"-->",
			"'",
			"<",
			">",
			'"',
			'&',
			'$',
			'=',
			';',
			'?',
			'/',
			"%20",
			"%22",
			"%3c",		// <
			"%253c",	// <
			"%3e",		// >
			"%0e",		// >
			"%28",		// (
			"%29",		// )
			"%2528",	// (
			"%26",		// &
			"%24",		// $
			"%3f",		// ?
			"%3b",		// ;
			"%3d"		// =
		);

		$file_name = stripslashes(str_replace($bad, '', $file_name));
		return preg_replace('/\s+/', '_', $file_name);
	}
	
	/*
	 * 是否允许的文件大小
	 * @access	protected
	 * @return	boolean
	 */
	protected function is_allowed_filesize()
	{	
		if ($this->max_size!=0 && $this->file_size>$this->max_size)
		{
			return FALSE;
		}
		return TRUE;
	}
	
	/*
	 * 是否允许的文件类型
	 * @access	protected
	 * @return	boolean
	 */
	protected function is_allowed_filetype()
	{
		if ( ! $this->allowed_types)
		{
			return TRUE;
		}
		return in_array($this->file_extension, $this->allowed_types);
	}
	
	/*
	 * 是否允许的尺寸，图片有效
	 * @access	protected
	 * @return	boolean
	 */
	protected function is_allowed_dimensions()
	{
		//不是图片，直接返回true
		if ( ! $this->is_image)
		{
			return TRUE;
		}
		
		if ($this->max_width && $this->image_width>$this->max_width)
		{
			return FALSE;
		}
		
		if ($this->max_height && $this->image_height>$this->max_height)
		{
			return FALSE;
		}
		
		return TRUE;
	}
	
	/*
	 * 返回后缀名
	 * @access	public
	 * @param	$filename string 文件名称
	 * @return	string
	 */
	public function get_extension($filename)
	{
		$a = explode('.', $filename);
		return end($a);
	}
	
	/*
	 * 移动文件，一切准备无误后统一上传
	 * @access	protected
	 * @return	boolean
	 */
	protected function move_file()
	{
		//移动文件
		//copy的效率要远高于move_uploaded_file
		if ( ! copy($this->file_temp, $this->upload_path.$this->file_name))
		{
			if ( ! move_uploaded_file($this->file_temp, $this->upload_path.$this->file_name))
			{
				return FALSE;
			}
		}
		return TRUE;
	}
	
	/*
	 * 删除已经上传的文件
	 * @access	protected
	 * @return	void
	 */
	protected function unlink_file()
	{
		if ($this->datas)
		{
			foreach ($this->datas as $data)
			{
				if (file_exists($data['upload_path'].$data['file_name']))
				{
					unlink($data['upload_path'].$data['file_name']);
				}
			}
		}
		$this->datas = array();
	}
	
	/*
	 * 保存数据
	 * @access	protected
	 * @return	void
	 */
	protected function save_data()
	{
		static $document_root;
		if ( ! isset($document_root))
		{
			$document_root = rtrim(str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']), '/');
		}
		
		$data = array(
			'upload_path' => $this->upload_path,
			'client_name' => $this->client_name,
			'original_name' => $this->original_name,
			'file_temp' => $this->file_temp,
			'file_name' => $this->file_name,
			'file_size' => $this->file_size,
			'file_type' => $this->file_type,
			'file_extension' => $this->file_extension,
			'is_image' => $this->is_image,
			'absolute_filename' => $this->upload_path.$this->file_name, //上传文件的绝对路径
			'relative_filename' => str_replace($document_root, '', $this->upload_path.$this->file_name) //上传文件的相对路径，相对于服务器根目录
		);
		
		if ($data['is_image'])
		{
			$data['image_width'] = $this->image_width;
			$data['image_height'] = $this->image_height;
			$data['img_size_str'] = 'width="'.$this->image_width.'" height="'.$this->image_height.'"';
		}
		
		$this->datas[] = $data;
		
		$this->client_name = '';
		$this->original_name = '';
		$this->file_temp = '';
		$this->file_size = 0;
		$this->file_type = '';
		$this->file_extension = '';
		$this->is_image = FALSE;
		$this->image_width = 0;
		$this->image_height = 0;
	}
	
	public function set_upload_path($upload_path)
	{
		$this->upload_path = trim($upload_path);
		return $this;
	}
	
	public function set_file_name($file_name)
	{
		$this->file_name = $this->clean_file_name(trim($file_name));
		return $this;
	}
	
	public function set_max_size($max_size)
	{
		$this->max_size = intval($max_size);
		return $this;
	}
	
	public function set_max_width($max_width)
	{
		$this->max_width = intval($max_width);
		return $this;
	}
	
	public function set_max_height($max_height)
	{
		$this->max_height = intval($max_height);
		return $this;
	}
	
	public function set_allowed_types($allowed_types)
	{
		if (is_string($allowed_types))
		{
			$s = strpos($allowed_types, '|') ? '|' : ',';
			$this->allowed_types = explode($s, $allowed_types);
		}
		elseif (is_array($allowed_types))
		{
			$this->allowed_types = $allowed_types;
		}
		
		$this->allowed_types = array_filter($this->allowed_types);
		return $this;
	}
	
	public function set_override($override)
	{
		$this->override = (boolean)$override;
		return $this;
	}

}